package com.such.kit.file;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipFile;
import com.such.kit.Callable;
import com.such.kit.file.bean.FileInfo;
import com.such.kit.file.support.FileFilter;
import com.such.kit.stream.StreamCommons;
import com.such.kit.string.StringCommons;
import com.such.kit.validate.ValidateCommons;

/**
 * @author SUCH
 * <pre>
 * 通用 zip 文件处理工具类
 * </pre>
 */
public class ZipFileCommons {

	/**
	 * <pre>
	 * [参考 {@link #listZip(String, String, FileFilter)}]
	 * charset 默认为 {@link StringCommons#CHARSET_UTF8 UTF-8}
	 * fileFilter 的默认值为 null
	 * </pre>
	 * @param zipPath zip 文件绝对路径
	 */
	public static List<FileInfo> listZip(String zipPath) 
			throws Exception {
		return listZip(zipPath, StringCommons.CHARSET_UTF8, null);
	}

	/**
	 * <pre>
	 * 列表 zip 文件内条目
	 * </pre>
	 * @param zipPath zip 文件绝对路径
	 * @param charset 字符集
	 * @param fileFilter 文件列表过滤器 [可空 为空时不做过滤]
	 * @return 条目列表
	 * @throws Exception
	 */
	public static List<FileInfo> listZip(String zipPath, String charset, FileFilter fileFilter) 
			throws Exception {
		final List<FileInfo> fileInfos = new ArrayList<FileInfo>();
		listZip(zipPath, charset, fileFilter, new Callable<FileInfo, Void>() {
			@Override
			public Void call(FileInfo fileInfo) throws Exception {
				fileInfos.add(fileInfo);
				return null;
			}
		});
		return fileInfos;
	}

	/**
	 * <pre>
	 * [参考 {@link #listZip(String, String, FileFilter, Callable)}]
	 * charset 默认为 {@link StringCommons#CHARSET_UTF8 UTF-8}
	 * fileFilter 的默认值为 null
	 * </pre>
	 * @param zipPath zip 文件绝对路径
	 * @param processor ZipEntry 处理器
	 */
	public static void listZip(String zipPath, Callable<FileInfo, Void> processor) 
			throws Exception {
		listZip(zipPath, StringCommons.CHARSET_UTF8, null, processor);
	}

	/**
	 * <pre>
	 * 列表 zip 文件内条目
	 * </pre>
	 * @param zipPath zip 文件绝对路径
	 * @param charset 字符集
	 * @param fileFilter 文件列表过滤器 [可空 为空时不做过滤]
	 * @param processor ZipEntry 处理器
	 * @throws Exception
	 */
	public static void listZip(String zipPath, String charset, FileFilter fileFilter, Callable<FileInfo, Void> processor) 
			throws Exception {
		ZipFile zipFile = null;
		try {
			zipFile = new ZipFile(zipPath, charset);
			Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
			while(entries.hasMoreElements()) {
				ZipArchiveEntry entry = entries.nextElement();
				FileInfo fileInfo = new FileInfo(zipPath, entry);
				if (ValidateCommons.isEmpty(fileFilter) || fileFilter.accept(fileInfo)) {
					processor.call(fileInfo);
				}
			}
		} finally {
			StreamCommons.close(zipFile);
		}
	}

	/**
	 * <pre>
	 * [参考 {@link #unpack(String, String, String)}]
	 * charset 默认为 {@link StringCommons#CHARSET_UTF8 UTF-8}
	 * targetPath 默认在 zip 文件所在目录下创建与 zip 文件同名的文件夹(不包括扩展名)
	 * </pre>
	 * @param zipPath zip 文件绝对路径
	 */
	public static void unpack(String zipPath) 
			throws Exception {
		unpack(zipPath, StringCommons.CHARSET_UTF8);
	}

	/**
	 * <pre>
	 * [参考 {@link #unpack(String, String, String)}]
	 * targetPath 默认在 zip 文件所在目录下创建与 zip 文件同名的文件夹(不包括扩展名)
	 * </pre>
	 * @param zipPath zip 文件绝对路径
	 * @param charset 字符集
	 */
	public static void unpack(String zipPath, String charset) 
			throws Exception {
		String path = FileCommons.cutLastPathNode(zipPath);
		String fileName = FileCommons.getLastPathNode(zipPath);
		String enlargeName = FileCommons.getEnlargeName(zipPath);
		String targetPath = FileCommons.appendPathNode(path, fileName.substring(0, fileName.length() - enlargeName.length() - 1));
		unpack(zipPath, charset, targetPath);
	}

	/**
	 * <pre>
	 * 解压 zip
	 * </pre>
	 * @param zipPath zip 文件绝对路径
	 * @param charset 字符集
	 * @param targetPath 目标绝对路径
	 * @throws Exception
	 */
	public static void unpack(String zipPath, String charset, String targetPath) 
			throws Exception {
		ZipFile zipFile = null;
		try {
			FileCommons.deleteFolder(targetPath);
			zipFile = new ZipFile(zipPath, charset);
			Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
			while(entries.hasMoreElements()) {
				ZipArchiveEntry entry = entries.nextElement();
				String outPath = FileCommons.appendPathNode(targetPath, entry.getName());
				if (entry.isDirectory()) {
					FileCommons.createFolder(outPath);
				} else {
					FileCommons.createFile(zipFile.getInputStream(entry), outPath);
				}
			}
		} finally {
			StreamCommons.close(zipFile);
		}
	}

	/**
	 * <pre>
	 * 打包文件夹
	 * 级联列表文件夹下的文件、文件夹
	 * </pre>
	 * @param folderPath 文件夹绝对路径
	 * @param zipPath zip 文件绝对路径
	 * @throws IOException 
	 */
	public static void packFolder(String folderPath, String zipPath, String charset) throws IOException {
		ZipArchiveOutputStream zipOut = null;
		try {
			zipOut = new ZipArchiveOutputStream(new File(zipPath));
			zipOut.setEncoding(charset);
			FileCommons.outputFolder(folderPath, zipOut);
			zipOut.finish();
		} finally {
			StreamCommons.close(zipOut);
		}
	}

	/**
	 * <pre>
	 * 打包文件到指定 zip 输出流
	 * </pre>
	 * @param filePath 文件绝对路径
	 * @param zipOut zip 输出流
	 * @param parentEntry 条目上级路径
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 */
	public static void packFile(String filePath, ZipArchiveOutputStream zipOut, String parentEntry) 
			throws FileNotFoundException, IOException {
		String entryName = FileCommons.getLastPathNode(filePath);
		if (ValidateCommons.isNotEmpty(parentEntry)) {
			entryName = StringCommons.merger(parentEntry, FileCommons.COMPAT_SEPARATOR, entryName);
		}
		packFile(entryName, new FileInputStream(filePath), zipOut);
	}

	/**
	 * <pre>
	 * 打包文件到指定 zip 输出流
	 * </pre>
	 * @param entryName 完整（包括路径）条目名
	 * @param filePath 文件绝对路径
	 * @param zipOut zip 输出流
	 * @throws IOException
	 */
	public static void packFile(String entryName, String filePath, ZipArchiveOutputStream zipOut) 
			throws IOException {
		packFile(entryName, new FileInputStream(filePath), zipOut);
	}

	/**
	 * <pre>
	 * 打包文件到指定 zip 输出流
	 * </pre>
	 * @param entryName 完整（包括路径）条目名
	 * @param in 输入流
	 * @param zipOut zip 输出流
	 * @throws IOException
	 */
	public static void packFile(String entryName, InputStream in, ZipArchiveOutputStream zipOut) 
			throws IOException {
		try {
			ZipArchiveEntry entry = new ZipArchiveEntry(entryName);
			zipOut.putArchiveEntry(entry);
			StreamCommons.binaryTransport(in, true, zipOut, false);
		} finally {
			if (ValidateCommons.isNotEmpty(zipOut)) {
				zipOut.closeArchiveEntry();
			}
		}
	}

}
